1. 函数高级¶
此部分作为对函数知识的补充,逻辑性比较差,行业比较乱。
1.1. 变量作用域¶
变量的作用域指的是变量的作何用范围。 每个变量有相应的作用范围限制,超出 作用范围的变量是不允许的。
变量按照作用域可以分类为:
全局(global): 定义在函数外部,整个程序内都可以使用
局部(local):在函数代码内部定义,超出某一段代码范围后就不能使用
变量的作用范围可以粗略的分为以下几种:
全局变量:在整个全局范围都有效
全局变量在局部可以使用(即函数内部可以方位函数外部定义的变量)
局部变量在局部范围可以使用
局部变量在全局范围无法使用
1.1.1. LEGB原则¶
Python
中可以把变量的作用域概括成LEGB
:
L(Local: 局部作用域
E(Enclosing function locale):外部嵌套函数作用域
G(Global module):函数定义所在模块作用域
B(Buildin):
Python
内置模块的作用域
参见以下案例:
# 认为a1是全局的
a1 = 100
def fun():
print(a1)
print("I am in fun")
# a2的作用范围是fun
a2 = 99
print(a2)
print(a1)
fun()
print(a2)
我们在函数外部定义了变量a1
, 函数内部对这个变量进行了打印操作,这样是可以的,函数内部可以直接使用
函数外部的变量, 同时函数内定义了变量a2
, 定义后再函数内使用这个变量没有问题,但我们在外部使用这个变量
就会报错,参加一下运行结果。
函数内部使用函数外部定义的变量,在上面的情况只限于对变量进行读操作,不能有赋值等修改操作
代码运行结果如下:
100
100
I am in fun
99
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-4-36e1efbb7d66> in <module>()
12 print(a1)
13 fun()
---> 14 print(a2)
NameError: name 'a2' is not defined
1.1.2. 提升局部变量为全局变量¶
如果我们试图在函数内修改函数外部定义的变量,此时需要使用关键字global
还用来告诉程序
要对函数外部定义的变量进行修改,即进行全局声明, 此时也可以把global
关键字理解成
是在函数内定义了一个作用域为全局的外部变量。
使用格式如下:
def 函数名()
#提升局部变量为全局变量
global 局部变量
其他操作...
参见以下案例:
def fun():
global b1
b1 = 100
print(b1)
print("I am in fun")
# a2的作用范围是fun
b2 = 99
print(b2)
#print(b2) #变量b2的作用域是局部,此处引用报错
fun()
print(b1)
代码运行结果如下, 需要注意的是变量b1
是在函数内部定义的, 只有当函数运行之后,即在函数
被调用后系统才会还有这个变量,在函数调用前使用这个变量会报错:
100
I am in fun
99
100
1.1.3. nonlocal-提升局部变量为非局部变量¶
nonlocal
的作用是声明当前变量不是当前函数内部的变量,他有可能是当前函数的外部变量(不是全局变量)。
语法格式为:
def 外部函数()
局部变量
def 内部函数():
nonlocal 变量名 #声明当前变量不是内部函数中的变量
其他代码...
return 代码
如果在内部函数中只是仅仅读外部变量,可以不在此变量前加
nonlocal
如果在内部函数中尝试进行修改外部变量,且外部变量为不可变类型,则需要在变量前加nonlocal
,如果变量为可变类型,则不需要添加
1.2. 函数的几个特殊用法¶
1.2.1. 函数的嵌套¶
函数嵌套是指一个函数里用def
关键字来创建其它函数的情况。
常见的格式如下:
def fun1():
print("fun1被调用)
def fun2():
print("fun2被调用")
fun2() #调用上面定义的函数
print("fun1调用结束")
1.2.2. 函数名字作为变量赋值¶
函数名可以看做普通变量,他在创建时绑定一个函数,此时可以对另外变量用函数名称赋值,此时被赋值的变量 可以看做是这个函数的第二个名字。
参加案例如下:
def fn():
print("hello world")
f1 = fn
f1() # 等同于fn()
1.2.3. 函数作为函数的参数¶
函数可以当做变量赋值,也可以当做参数传入另外函数内部使用。
参考下面代码:
def myadd(x, y):
return x + y
def mymul(x, y):
return x * y
def getfun(fx):
if "add" == fx:
return myadd
elif "mul" == fx:
return mymul
return None
a = int(input("请输入第一个数"))
b = int(input("请输入第二个数"))
op = int(input("请输入操作方法:(mul/add):"))
f = getfun(op)
print(f(a,b)) #注意函数的调用方式
1.3. 递归函数¶
函数写完以后被调用才会运行。
函数代码里可以继续调用其他函数,但如果函数调用自己,则此类函数叫做递归函数,即函数直接或间接调用 自己。
递归函数的优点为简洁和理解容易, 还有同学会觉得很难理解,但你是不理解递归的定义导致的,在有些特定的场合 如果不用递归的话,直接写出来的程序是很复杂的。
递归函数的缺点是系统对递归深度有限制,而且递归函数消耗资源大。
Python
对递归深度有限制,即函数调用自己的次数有限制, 超过限制报错
在编写递归函数时,一定要注意递归函数的结束条件,递归函数不能无限制递归调用下去,否则会好近计算机所有资源。
下面案例是一个失败的递归调用案例,因为没有对结束条件进行规定导致递归函数一直运行:
# 递归调用深度限制代码
x = 0
def fun():
global x
x += 1
print(x)
# 函数自己调用自己
fun()
# 调用函数
fun()
函数运行结果如下, 注意最后的报错,是递归错误:
1
2
3
4
5
6
7
8
140
---------------------------------------------------------------------------
RecursionError Traceback (most recent call last)
<ipython-input-15-342ffb5916b5> in <module>()
11
12 # 调用函数
---> 13 fun()
<ipython-input-15-342ffb5916b5> in fun()
8 print(x)
9 # 函数自己调用自己
---> 10 fun()
11
12 # 调用函数
... last 1 frames repeated, from the frame below ...
<ipython-input-15-342ffb5916b5> in fun()
8 print(x)
9 # 函数自己调用自己
---> 10 fun()
11
12 # 调用函数
RecursionError: maximum recursion depth exceeded in comparison
1.3.1. 斐波那契数列¶
递归函数一个经典的案例是斐波那契数列,参考一下代码:
# 斐波那契额数列
# 一列数字,第一个值是1, 第二个也是1, 从第三个开始,每一个数字的值等于前两个数字出现的值的和
# 数学公式为: f(1) = 1, f(2) = 1, f(n) = f(n-1) + f(n-2)
# 例如: 1,1,2,3,5,8,13.。。。。。。。。
# 下面求斐波那契数列函数有一定问题,比如n一开始就是负数,如何修正
# n表示求第n个数子的斐波那契数列的值
def fib(n):
if n == 1:
return 1
if n == 2:
return 1
# 思考:为什么后面return能够正确执行,而不用else语句
return fib(n-1) + fib(n-2)
print(fib(3))
print(fib(10))
代码运行结果如下:
2
55
1.4. 系统内置函数¶
Python
有一些内置的函数,都封装在了__builtins__
模块中,可以通过 print((dir(__builtins__)))
来打印出所有内置函数和功能。
下面列举几个常用的内置函数:
1.4.1. eval()函数¶
在Python
中,eval
函数可以把一个字符串当成一个表达式来执行, 返回表达式执行后的结果作为函数执行
的结果。
函数的定义接口为eval(string_code, globals=None, locals=None)
, 其中:
string_code
: 被当做命令执行的字符串globals
: 执行代码时候的全局变量,作为执行环境参数locals
: 执行代码时候的局部变量,作为执行环境参数
代码案例参考如下:
x = 100
y = 200
# 执行x+y
# z = x + y
z1 = x + y
z2 = eval("x+y")
print(z1)
print(z2)
运行结果为:
300
300
1.4.2. exec()函数¶
跟eval
功能类似, 但是不返回结果.
函数的定义接口为exec(string_code, globals=None, locals=None)
代码案例参考如下:
# exec示例
x = 100
y = 200
# 执行x+y
# z = x + y
z1 = x + y
# 1, 注意字符串中引号的写法
# 2. 比对exec执行结果和代码执行结果
z2 = exec("print('x+y:', x+y)")
print(z1)
print(z2)
运行结果为:
x+y: 300
300
None
1.4.3. repr()函数¶
repr函数用来取得对象的规范字符串表示。
反引号(也称转换符)可以完成相同的功能。
注意,在大多数时候有eval(repr(object))==object。
可以通过定义类__repr__方法来控制你的对象在被repr函数调用的时候返回内容
1.4.4. compile()函数¶
compile
语句是从type类型中将str里面的语句创建成代码对象,以供以后执行。
type:
eval: 配合eval使用
single: 配合单一语句的exec使用
exec: 配合多语句的exec使用
file 是代码存放的地方,通常为空字符串
参看以下代码:
>>> eval_code = compile( '1+2', '', 'eval')
>>> eval_code
<code object <module> at 0142ABF0, file "", line 1>
>>> eval(eval_code)
3
>>> single_code = compile( 'print "www.baoshu.red"', '', 'single' )
>>> single_code
<code object <module> at 01C68848, file "", line 1>
>>> exec(single_code)
www.baoshu.red
>>> exec_code = compile( """for i in range(5):
... print "iter time: %d" % i""", '', 'exec' )
>>> exec_code
<code object <module> at 01C68968, file "", line 1>
>>> exec(exec_code)
iter time: 0
iter time: 1
iter time: 2
iter time: 3
iter time: 4
1.4.5. globals, locals函数¶
在代码中我们可以通过globals,locals
两个函数显示出我们所有的局部变量和全局变量。
# globals 和 locals
# globals 和 locals 叫做内建函数
a = 1
b = 2
def fun(c,d):
e = 111
print("Locals={0}".format(locals()))
print("Globals={0}".format(globals()))
fun(100, 200)
代码运行结果如下:
Locals={'e': 111, 'd': 200, 'c': 100}
Globals={'__name__': '__main__', '__doc__': 'Automatically created module for IPython interactive environment', '__package__': None, '__loader__': None, '__spec__': None, '__builtin__': <module 'builtins' (built-in)>, '__builtins__': <module 'builtins' (built-in)>, '_ih': ['', '# 认为a1是全局的\na1 = 100\n\ndef fun():\n print(a1)\n print("I am in fun")\n # a2的作用范围是fun\n a2 = 99\n print(a2)\n \n \nprint(a1)\n#print(a2)', '# 认为a1是全局的\na1 = 100\n\ndef fun():\n print(a1)\n print("I am in fun")\n # a2的作用范围是fun\n a2 = 99\n print(a2)\n \n \nprint(a1)\nfun()\n#print(a2)', '# 认为a1是全局的\na1 = 100\n\ndef fun():\n print(a1)\n print("I am in fun")\n # a2的作用范围是fun\n a2 = 99\n print(a2)\n \n \nprint(a1)\nfun()\nprint(a2)', '# 认为a1是全局的\na1 = 100\n\ndef fun():\n print(a1)\n print("I am in fun")\n # a2的作用范围是fun\n a2 = 99\n print(a2)\n \n \nprint(a1)\nfun()\nprint(a2)', '\ndef fun():\n b1 = 100\n print(b1)\n print("I am in fun")\n # a2的作用范围是fun\n b2 = 99\n print(b2)\n \n \nprint(b1)\nprint(b2)\nfun()', '\ndef fun():\n global b1 = 100\n print(b1)\n print("I am in fun")\n # a2的作用范围是fun\n b2 = 99\n print(b2)\n \n \nprint(b1)\n#print(b2)\nfun()', '\ndef fun():\n global b1\n b1 = 100\n print(b1)\n print("I am in fun")\n # a2的作用范围是fun\n b2 = 99\n print(b2)\n \n \nprint(b1)\n#print(b2)\nfun()', '\ndef fun():\n global b1\n b1 = 100\n print(b1)\n print("I am in fun")\n # a2的作用范围是fun\n b2 = 99\n print(b2)\n \n \nprint(b1)\n#print(b2)\nfun()', '\ndef fun():\n b1 = 100\n global b1\n b1 = 100\n print(b1)\n print("I am in fun")\n # a2的作用范围是fun\n b2 = 99\n print(b2)\n \n \nprint(b1)\n#print(b2)\nfun()', '# globals 和 locals\na = 1\nb = 2\n\ndef fun(c,d):\n e = 111\n print("Locals={0}".format(locals()))\n print("Globals={0}".format(globals()))\n \nfun(100, 200)'], '_oh': {}, '_dh': ['/home/tlxy/cookbook_and_code'], 'In': ['', '# 认为a1是全局的\na1 = 100\n\ndef fun():\n print(a1)\n print("I am in fun")\n # a2的作用范围是fun\n a2 = 99\n print(a2)\n \n \nprint(a1)\n#print(a2)', '# 认为a1是全局的\na1 = 100\n\ndef fun():\n print(a1)\n print("I am in fun")\n # a2的作用范围是fun\n a2 = 99\n print(a2)\n \n \nprint(a1)\nfun()\n#print(a2)', '# 认为a1是全局的\na1 = 100\n\ndef fun():\n print(a1)\n print("I am in fun")\n # a2的作用范围是fun\n a2 = 99\n print(a2)\n \n \nprint(a1)\nfun()\nprint(a2)', '# 认为a1是全局的\na1 = 100\n\ndef fun():\n print(a1)\n print("I am in fun")\n # a2的作用范围是fun\n a2 = 99\n print(a2)\n \n \nprint(a1)\nfun()\nprint(a2)', '\ndef fun():\n b1 = 100\n print(b1)\n print("I am in fun")\n # a2的作用范围是fun\n b2 = 99\n print(b2)\n \n \nprint(b1)\nprint(b2)\nfun()', '\ndef fun():\n global b1 = 100\n print(b1)\n print("I am in fun")\n # a2的作用范围是fun\n b2 = 99\n print(b2)\n \n \nprint(b1)\n#print(b2)\nfun()', '\ndef fun():\n global b1\n b1 = 100\n print(b1)\n print("I am in fun")\n # a2的作用范围是fun\n b2 = 99\n print(b2)\n \n \nprint(b1)\n#print(b2)\nfun()', '\ndef fun():\n global b1\n b1 = 100\n print(b1)\n print("I am in fun")\n # a2的作用范围是fun\n b2 = 99\n print(b2)\n \n \nprint(b1)\n#print(b2)\nfun()', '\ndef fun():\n b1 = 100\n global b1\n b1 = 100\n print(b1)\n print("I am in fun")\n # a2的作用范围是fun\n b2 = 99\n print(b2)\n \n \nprint(b1)\n#print(b2)\nfun()', '# globals 和 locals\na = 1\nb = 2\n\ndef fun(c,d):\n e = 111\n print("Locals={0}".format(locals()))\n print("Globals={0}".format(globals()))\n \nfun(100, 200)'], 'Out': {}, 'get_ipython': <bound method InteractiveShell.get_ipython of <ipykernel.zmqshell.ZMQInteractiveShell object at 0x7f891e18d198>>, 'exit': <IPython.core.autocall.ZMQExitAutocall object at 0x7f8914136fd0>, 'quit': <IPython.core.autocall.ZMQExitAutocall object at 0x7f8914136fd0>, '_': '', '__': '', '___': '', '_i': '\ndef fun():\n b1 = 100\n global b1\n b1 = 100\n print(b1)\n print("I am in fun")\n # a2的作用范围是fun\n b2 = 99\n print(b2)\n \n \nprint(b1)\n#print(b2)\nfun()', '_ii': '\ndef fun():\n global b1\n b1 = 100\n print(b1)\n print("I am in fun")\n # a2的作用范围是fun\n b2 = 99\n print(b2)\n \n \nprint(b1)\n#print(b2)\nfun()', '_iii': '\ndef fun():\n global b1\n b1 = 100\n print(b1)\n print("I am in fun")\n # a2的作用范围是fun\n b2 = 99\n print(b2)\n \n \nprint(b1)\n#print(b2)\nfun()', '_i1': '# 认为a1是全局的\na1 = 100\n\ndef fun():\n print(a1)\n print("I am in fun")\n # a2的作用范围是fun\n a2 = 99\n print(a2)\n \n \nprint(a1)\n#print(a2)', 'a1': 100, 'fun': <function fun at 0x7f88fbfb5268>, '_i2': '# 认为a1是全局的\na1 = 100\n\ndef fun():\n print(a1)\n print("I am in fun")\n # a2的作用范围是fun\n a2 = 99\n print(a2)\n \n \nprint(a1)\nfun()\n#print(a2)', '_i3': '# 认为a1是全局的\na1 = 100\n\ndef fun():\n print(a1)\n print("I am in fun")\n # a2的作用范围是fun\n a2 = 99\n print(a2)\n \n \nprint(a1)\nfun()\nprint(a2)', '_i4': '# 认为a1是全局的\na1 = 100\n\ndef fun():\n print(a1)\n print("I am in fun")\n # a2的作用范围是fun\n a2 = 99\n print(a2)\n \n \nprint(a1)\nfun()\nprint(a2)', '_i5': '\ndef fun():\n b1 = 100\n print(b1)\n print("I am in fun")\n # a2的作用范围是fun\n b2 = 99\n print(b2)\n \n \nprint(b1)\nprint(b2)\nfun()', '_i6': '\ndef fun():\n global b1 = 100\n print(b1)\n print("I am in fun")\n # a2的作用范围是fun\n b2 = 99\n print(b2)\n \n \nprint(b1)\n#print(b2)\nfun()', '_i7': '\ndef fun():\n global b1\n b1 = 100\n print(b1)\n print("I am in fun")\n # a2的作用范围是fun\n b2 = 99\n print(b2)\n \n \nprint(b1)\n#print(b2)\nfun()', '_i8': '\ndef fun():\n global b1\n b1 = 100\n print(b1)\n print("I am in fun")\n # a2的作用范围是fun\n b2 = 99\n print(b2)\n \n \nprint(b1)\n#print(b2)\nfun()', '_i9': '\ndef fun():\n b1 = 100\n global b1\n b1 = 100\n print(b1)\n print("I am in fun")\n # a2的作用范围是fun\n b2 = 99\n print(b2)\n \n \nprint(b1)\n#print(b2)\nfun()', '_i10': '# globals 和 locals\na = 1\nb = 2\n\ndef fun(c,d):\n e = 111\n print("Locals={0}".format(locals()))\n print("Globals={0}".format(globals()))\n \nfun(100, 200)', 'a': 1, 'b': 2}
1.5. 函数式编程(FunctionalProgramming)和高阶函数¶
函数式编程
是基于lambda演算的一种编程方式, 在函函数式编程
中程序只有函数,其中函数可以作为参数,同样可以作为返回值。
纯函数式编程语言有LISP
,Haskell
等, 但Python
中要讲的函数式编程只是借鉴函数式编程的一些特点,可以理解成一半函数式一半Python
。
1.5.1. lambda表达式¶
函数的作用是最大程度复用代码,但是也存在一些问题,比如如果函数的代码很少或很短:
会造成程序很细碎,反而显得比较啰嗦。
如果函数被调用次数少,则会造成资源浪费,比较每次调用函数需要一定的系统资源开销
对于阅读者来说,频繁的跳转会造成阅读流程的被迫中断
对于一些比较短小的代码,我们可以尝试使用匿名函数来替代一个比较常规的函数的定义流程,匿名函数也叫lambda
表达式:
是一个表达式,代替函数体相对简单的函数来定义和执行
不是一个代码块,仅仅是一个表达式
可以有参数,甚至客户已有多个参数,但需要用逗号隔开不同的参数
下面的案例是一段很小的代码,也是一个很简短的函数:
# “小”函数举例
def printA():
print("AAAAAAAA")
printA() #调用了函数
下面我们聊下lambda
表达是的具体用法:
以lambda开头
紧跟一定的参数(如果有的话)
参数后用冒号和表达式主题隔开
只是一个表达式,所以,没有
return
具体请参照下面的案例来理解:
# 计算一个数字的100倍数
# 因为就是一个表达式,所以没有return
stm = lambda x: 100 * x
# 使用上跟函数调用一模一样
stm(89)
或者下面也是也是一个多参数的案例:
stm2 = lambda x,y,z: x+ y*10 + z*100
stm2(4,5,6) #执行表达式
1.5.2. 高阶函数¶
函数的参数一般是一个值,但函数的名称同样可以作为一个值传递,把函数作为参数使用的函数,叫高阶函数
。
下面的案例中funA
是被定义的一个函数,然后把这个函数名字作为值赋值给funB
,此时funB,funA
都指向了一个共同的函数体,在
调用的时候可以用任何一个名字,结果相同。
# 变量可以赋值
a = 100
b = a
# 函数名称就是一个变量
def funA():
print("In funA")
funB = funA
funB()
以上代码得出的结论:
函数名称可以作为值,也可以是变量
funB
和funA
只是名称不一样而已
既然函数名称是变量,则应该可以被当做参数传入另一个函数, 这就是所谓高阶函数的基础。
下面案例展示函数的普通调用方式,即在函数内部调用别的函数:
# 高阶函数举例
# funA是普通函数,返回一个传入数字的100倍数字
def funA(n):
return n * 100
# 再写一个函数,把传入参数乘以300倍,
def funB(n ):
# 最终是想返回300n
return funA(n) * 3
print(funB(9))
上面代码是可以的,但如果想办法降低这两个函数的耦合度,就可以用高阶函数,即把函数funA
作为参数传入函数funB
:
# 写一个高阶函数
def funC(n, f):
# 假定函数是把n扩大100被
return f(n) * 3
print( funC(9, funA) )
比较funC
和funB
, 显然funC
的写法要优于funB
, funC
的功能如果后期面临维护或者修改,要比funB
灵活许多,
例如我们需要替换funA
的功能部分,根本不需要修改代码funB
,只需要传入不同的函数funD
即可:
def funD(n):
return n*10
# 需求变更,需要把n放大三十倍,此时funB则无法实现
print(funC(7, funD))
1.5.3. map¶
map
原意就是映射,即把集合或者列表的元素,每一个元素都按照一定规则进行操作,结构生成一个新的列表或者集合。
在Python
中map
函数是系统提供的具有映射功能的函数,返回值是一个迭代对象。
通过查询help(map)
的帮助文档可以清晰的显示他的用法:
Help on class map in module builtins:
class map(object)
| map(func, *iterables) --> map object
|
| Make an iterator that computes the function using arguments from
| each of the iterables. Stops when the shortest iterable is exhausted.
map
的具体使用请参阅下面案例:
如果有一个列表,想对列表里的每一个元素乘以10并得到换一个新的列表,我们可以通过以前的知识遍历列表来完成这个功能, 例如下面代码:
l1 = [i for i in range(10)]
print(l1)
l2 = []
for i in l1:
l2.append(i * 10)
但是如果我们用map
函数实现就简单的多了,先编写一个函数,这个函数是map
用来映射后进行操作的函数:
# 利用map实现
def mulTen(n):
return n*10
然我们调用map
, 可以理解成用l1的每个元素逐个调用mulTen函数,然后放入结果中生成一个新的列表,虽然这种理解并不准确,
准确的说法是生成了换一个迭代器,对数据的映射操作也不是一次性完成全部,二是逐次完成:
l3 = map(mulTen, l1 )
# map类型是一个可迭代的结构,所以可以使用for遍历
for i in l3:
print(i)
1.5.4. reduce¶
reduce
原意是归并或者缩减,在这里代表把一个可迭代的对象归并成一个结果。
reduce
在functools
模块里
reduce
的帮助文档如下:
reduce(...)
reduce(function, sequence[, initial]) -> value
Apply a function of two arguments cumulatively to the items of a sequence,
from left to right, so as to reduce the sequence to a single value.
For example, reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) calculates
((((1+2)+3)+4)+5). If initial is present, it is placed before the items
of the sequence in the calculation, and serves as a default when the
sequence is empty.
reduce
会把一个迭代对象中的内容,先把前两个元素作为才是调用某个函数,然后把函数结果作为一个参数,迭代对象
的第三个元素作为第二个参数再次调用函数,这样逐次调用迭代对象的全部参数,知道最后。
对于作为参数的函数有两个要求:
必须有两个参数
必须有返回结果
参加下面案例:
from functools import reduce
# 定义一个操作函数
# 操作函数要有两个参数和一个
def myAdd(x,y):
return x + y
# 对于列表[1,2,3,4,5,6]执行myAdd的reduce操作
rst = reduce( myAdd, [1,2,3,4,5,6] )
print(rst)
1.5.5. filter¶
过滤函数,对一组数据进行过滤,符合条件的数据会生成一个新的列表并返回。
filter
跟map
相比较:
相同点:都对列表的每一个元素逐一进行操作
不同:
map
会生成一个跟原来数据相对应的新队列filter
不一定,只要符合条件的才会进入新的数据集合
filter
函数怎么写:
利用给定参数进行判断
返回值一定是个布尔值
调用格式:
filter(f, data)
,f
是过滤函数,data
是数据
filter
函数的一个案例是:
# 对于一个列表,对其进行过滤,偶数组成一个新列表
# 需要定义过滤函数, 过滤函数要求有输入,返回布尔值
def isEven(a):
return a % 2 == 0
l = [3,4,56,3,2,3,4556,67,4,4,3,23455,43]
rst = filter(isEven, l)
# 返回的filter内容是一个可迭代对象
print(type(rst))
print(rst)
1.5.6. sorted¶
sorted
把一个序列按照给定算法进行排序, 函数的帮助文档如下:
sorted(iterable, /, *, key=None, reverse=False)
Return a new list containing all items from the iterable in ascending order.
A custom key function can be supplied to customize the sort order, and the
reverse flag can be set to request the result in descending order.
在sorted
的参数中, key
参数代表的函数在排序前对每一个元素进行key函数运算,可以理解成按照key函数定义的逻辑进行排序
sorted
函数在python2
和python3
相差巨大, 此处以python3
为准
关于排序参考下面案例:
a = [234,22312,123,45,43,2,3,66723,34]
al = sorted(a, reverse=True)
print(al)
上面案例并没有时候用参数key
, 下面案例将调用排序函数:
a = [-43,23,45,6,-23,2,-4345]
# 按照绝对值进行排序
# abs是求绝对值的意思
# 即按照绝对值的倒叙排列
al = sorted(a, key=abs, reverse=True)
下面案例通过调用字符串的小写转换函数,先将所有需要排序的字符串转换,然后排序:
astr = ['dana', 'Danaa', 'wangxiaojing', 'jingjing', 'haha']
str1 = sorted(astr)
print(str1)
str2 = sorted(astr, key=str.lower)
print(str2)
1.6. 装饰器¶
1.6.1. 函数作为返回值¶
函数调用可以返回具体的值,当然也可以返回一个函数作为结果。
函数作为返回值返回的时候,被返回的函数需要在函数体内定义,如下所示:
def myF2():
def myF3():
print("In myF3")
return 3
return myF3
# 使用上面定义, 调用myF2,返回一个函数myF3,赋值给f3
f3 = myF2() #得到的是函数myF3,此时f3和myF3是同一个函数
print(type(f3))
f3()
下面的案例返回一个函数,但返回函数中,使用了外层函数的某些值,这些值可能是外层函数的参数,也可能是外层函数中 定义的一些变量:
# args:参数列表
# 1 myF4定义函数,返回内部定义的函数myF5
# 2. myF5使用了外部变量,这个变量是myF4的参数
def myF4( *args):
def myF5():
rst = 0
for n in args: #内部函数使用了外部函数的值
rst += n
return rst
return myF5
上面函数的使用:
f5 = myF4(1,2,3,4,5,6,7,8,9,0)
# f5的调用方式
f5()
1.6.2. 闭包(closure)¶
当一个函数在内部又定义了一个函数,并且内部的函数使用了外部函数的参数或者局部变量,当内部函数被当做返回值的时候,
相关参数和变量保存在返回的函数中,这种结果,叫闭包
上面定义的myF4
是一个标准闭包结构。
1.6.3. 闭包常见坑¶
闭包函数使用起来可能会遇到坑,要小心:
def count():
# 定义列表,列表里存放的是定义的函数
fs = []
for i in range(1, 4):
# 定义了一个函数f
# f是一个闭包结构
def f():
return i * i
fs.append(f)
return fs
f1, f2, f3 = count()
print(f1())
print(f2())
print(f3())
运行结果为:
C:\Users\Augs\Anaconda3\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2020.2.3\plugins\python-ce\helpers\pydev\pydevd.py" --multiproc --qt-support=auto --client 127.0.0.1 --port 52813 --file F:/book/python/new_python/code/te.py
pydev debugger: process 7664 is connecting
Connected to pydev debugger (build 202.7660.27)
9
9
9
Process finished with exit code 0
出现的问题分析:
造成上述状况的原因是,返回函数引用了变量i, i并非立即执行,而是等到三个函数都返回的时候才统一使用,此时i已经变成了3,最终调用的时候,都返回的是 3*3
此问题描述成:返回闭包时,返回函数不能引用任何循环变量
解决方案: 再创建一个函数,用该函数的参数绑定循环变量的当前值,无论该循环变量以后如何改变,已经绑定的函数参数值不再改变
修改上述函数:
def count2():
def f(j):
def g():
return j*j
return g
fs = []
for i in range(1,4):
fs.append(f(i))
return fs
f1,f2,f3 = count2()
print(f1())
print(f2())
print(f3())
修改后的函数运行和能达到预期.
1.6.4. 当变量使用的函数¶
请看下面平淡无奇的案例:
def hello():
print("Hello world")
hello()
函数名称可以作为变量进行赋值:
f = hello
f()
f和hello是一个函数, 这两个函数名称不同,但id
相同,通过下面代码可以验证:
print(id(f))
print(id(hello))
print(f.__name__)
print(f.__name__)
现在我们有了新的需求:
对hello功能进行扩展,每次打印hello之前打印当前系统时间
而实现这个功能又不能改动现有代码
此时可以解逐装饰器来实现.
1.6.5. 装饰器(Decrator)¶
在不改动函数代码的基础上无限制扩展函数功能的一种机制,本质上讲,装饰器是一个能够返回函数的高阶函数
装饰器的使用: 使用@语法, 即在每次要扩展到函数定义前使用@+函数名
任务:
对hello函数进行功能扩展,每次执行hello万打印当前时间
利用下面的代码, 及定义一个装饰器, 装饰要hello
函数:
import time
# 高阶函数,以函数作为参数
def printTime(f):
def wrapper(*args, **kwargs):
print("Time: ", time.ctime())
return f(*args, **kwargs)
return wrapper
# 上面定义了装饰器,使用的时候需要用到@, 此符号是python的语法糖
@printTime
def hello():
print("Hello world")
#利用语法糖调用
hello()
函数的运行结果如下:
C:\Users\Augs\Anaconda3\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2020.2.3\plugins\python-ce\helpers\pydev\pydevd.py" --multiproc --qt-support=auto --client 127.0.0.1 --port 55245 --file F:/book/python/new_python/code/te.py
pydev debugger: process 12548 is connecting
Connected to pydev debugger (build 202.7660.27)
Time: Mon Dec 27 18:34:36 2021
Hello world
Process finished with exit code 0
装饰器的好处是:一点定义,则可以装饰任意函数 而函数一旦被其装饰,则把装饰器的功能直接添加到定义函数的功能上, 但对函数原本的代码功能没有任何副作用.
参看下面代码:
@printTime
def hello2():
print("今天很高兴,被老板揪着讲课了")
print("还可以由很多的选择")
hello2()
上面对函数的装饰使用了系统定义的语法糖, 下面我们尝试手动执行下装饰器
我们的代码首次按使用定义好的的装饰器, 但我们手动调用这个装饰器,把相应函数传入, 调用有两种情况,一种是 调用装饰器后返回的结果放入原来被装饰的函数名:
hello3 = printTime(hello3)
另一种是返回结果单独起了一个名字:
f = printTime(hello3)
两种不同调用也引发了不同结果,思考下,为什么会这样?
import time
# 高阶函数,以函数作为参数
def printTime(f):
def wrapper(*args, **kwargs):
print("Time: ", time.ctime())
return f(*args, **kwargs)
return wrapper
def hello3():
print("我是手动执行的")
hello3()
print("*" * 40)
hello3 = printTime(hello3)
hello3()
print("*" * 40)
f = printTime(hello3)
f()
运行结果如下:
C:\Users\Augs\Anaconda3\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2020.2.3\plugins\python-ce\helpers\pydev\pydevd.py" --multiproc --qt-support=auto --client 127.0.0.1 --port 55607 --file F:/book/python/new_python/code/te.py
pydev debugger: process 12188 is connecting
Connected to pydev debugger (build 202.7660.27)
我是手动执行的
****************************************
Time: Mon Dec 27 18:40:00 2021
我是手动执行的
****************************************
Time: Mon Dec 27 18:40:00 2021
Time: Mon Dec 27 18:40:00 2021
我是手动执行的
Process finished with exit code 0
思考题答案: 装饰器返回结果的代码
ruturn f(...)
是一个执行语句, 如果返回语句名称一样, 则覆盖一个, 否则会执行一个f
函数一个hello3
函数 再不明白可以进QQ群: 999 0977
, 或者微信13119144223
1.7. 偏函数¶
我们先看一个例子, 把字符串转化成十进制数字:
int("12345")
上面例子很简单,我们还可以通过这个函数求八进制的字符串12345,表示成十进制的数字是多少:
int("12345", base=8)
我们新建一个函数,此函数是默认输入的字符串是16进制数字, 把此字符串返回十进制的数字的用法是:
def int16(x, base=16):
return int(x, base)
int16("12345")
代码很好理解,不做过多解释了.
1.7.1. 偏函数¶
偏函数是参数固定的函数,相当于一个由特定参数的一个函数, 实现偏函数需要用到functools.partial
:
functools.partial的作用是,把一个函数某些函数固定,返回一个新函数
参看下面案例:
import functools
#实现上面int16的功能
int16 = functools.partial(int, base=16)
int16("12345")
1.8. 高级函数补充¶
1.8.1. zip¶
把两个可迭代内容生成一个可迭代的tuple元素类型组成的内容
zip
案例:
l1 = [ 1,2,3,4,5]
l2 = [11,22,33,44,55]
z = zip(l1, l2)
print(type(z))
print(z)
运行结果如下:
C:\Users\Augs\Anaconda3\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2020.2.3\plugins\python-ce\helpers\pydev\pydevd.py" --multiproc --qt-support=auto --client 127.0.0.1 --port 56228 --file F:/book/python/new_python/code/te.py
pydev debugger: process 11868 is connecting
Connected to pydev debugger (build 202.7660.27)
<class 'zip'>
<zip object at 0x00000213421CED88>
Process finished with exit code 0
另一个例子:
l1 = ["wangwang", "mingyue", "yyt"]
l2 = [89, 23, 78]
z = zip(l1, l2)
for i in z:
print(i)
运行结果如下:
C:\Users\Augs\Anaconda3\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2020.2.3\plugins\python-ce\helpers\pydev\pydevd.py" --multiproc --qt-support=auto --client 127.0.0.1 --port 56172 --file F:/book/python/new_python/code/te.py
pydev debugger: process 12704 is connecting
Connected to pydev debugger (build 202.7660.27)
('wangwang', 89)
('mingyue', 23)
('yyt', 78)
<class 'zip'>
Process finished with exit code 0
考虑下面结果,为什么会为空
l3 = [i for i in z]
print(l3)
上面是zip的结果是一个迭代器, 迭代器运行到最后就是返回空,参看迭代器知识点
1.8.2. enumerate¶
跟zip功能比较像
对可迭代对象里的每一元素,配上一个索引,然后索引和内容构成tuple类型
enumerate案例1:
l1 = [11,22,33,44,55]
em = enumerate(l1)
l2 = [i for i in em]
print(l2)
运行结果如下:
C:\Users\Augs\Anaconda3\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2020.2.3\plugins\python-ce\helpers\pydev\pydevd.py" --multiproc --qt-support=auto --client 127.0.0.1 --port 56285 --file F:/book/python/new_python/code/te.py
pydev debugger: process 800 is connecting
Connected to pydev debugger (build 202.7660.27)
[(0, 11), (1, 22), (2, 33), (3, 44), (4, 55)]
Process finished with exit code 0
另一个比较飘逸的例子是:
l1 = [11,22,33,44,55]
em = enumerate(l1, start=100)
l2 = [ i for i in em]
print(l2)
结果为:
C:\Users\Augs\Anaconda3\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2020.2.3\plugins\python-ce\helpers\pydev\pydevd.py" --multiproc --qt-support=auto --client 127.0.0.1 --port 56381 --file F:/book/python/new_python/code/te.py
pydev debugger: process 13092 is connecting
Connected to pydev debugger (build 202.7660.27)
[(100, 11), (101, 22), (102, 33), (103, 44), (104, 55)]
Process finished with exit code 0
1.9. collections模块¶
collections
包含了一些特殊的容器,针对Python
内置的容器,例如list
、dict
、set
和tuple
,提供了另一种选择;
namedtuple
,可以创建包含名称的tuple;deque
: 类似于list的容器,可以快速的在队列头部和尾部添加、删除元素;Counter
: dict的子类,计算可hash的对象;OrderedDict
: dict的子类,可以记住元素的添加顺序;defaultdict
: dict的子类,可以调用提供默认值的函数;
1.9.1. namedtuple¶
命名的元组,意味给元组中的每个位置赋予含义,意味着代码可读性更强,
namedtuple
可以在任何常规元素使用的地方使用,而且它可以通过名称来获取字段信息而不仅仅是通过位置索引。
是一个
tuple
类型是一个可命名的
tuple
namedtuple
在给csv
或者sqlite3
返回的元组附上名称特别有用
请看下面例子:
>>> import collections
>>> Point = namedtuple('Point', ['x', 'y'])
>>> Point.__doc__ # docstring for the new class
'Point(x, y)'
>>> p = Point(11, y=22) # instantiate with positional args or keywords
>>> p[0] + p[1] # indexable like a plain tuple
33
>>> x, y = p # unpack like a regular tuple
>>> x, y
(11, 22)
>>> p.x + p.y # fields also accessible by name
33
>>> d = p._asdict() # convert to a dictionary
>>> d['x']
11
>>> Point(**d) # convert from a dictionary
Point(x=11, y=22)
>>> p._replace(x=100) # _replace() is like str.replace() but targets named fields
Point(x=100, y=22)
从某种意义上说, 相当于直接创建了一个类, 这个类名称是Point
, 有两个属性, x, y
下面这段代码如果不看Circle
的定义, 级别就是一个定义的类而已:
Circle = collections.namedtuple("Circle", ['x', 'y', 'r'])
c = Circle(100, 150, 50)
print(c)
print(type(c))
运行结果如下:
C:\Users\Augs\Anaconda3\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2020.2.3\plugins\python-ce\helpers\pydev\pydevd.py" --multiproc --qt-support=auto --client 127.0.0.1 --port 51985 --file F:/book/python/new_python/code/te.py
pydev debugger: process 16160 is connecting
Connected to pydev debugger (build 202.7660.27)
Circle(x=100, y=150, r=50)
<class '__main__.Circle'>
Process finished with exit code 0
想检测以下namedtuple
到底属于谁的子类
isinstance(c, tuple)
运行结果是:
True
1.9.2. dequeue¶
deque
是栈和队列的一种广义实现,是”double-end queue”的简称;
deque
支持线程安全、有效内存地以近似o(1)
的性能在deque
的两端插入和删除元素尽管
list
也支持相似的操作,但是它主要在固定长度操作上的优化,从而在pop(0)
和insert(0,v)
(会改变数据的位置和大小)上有o(n)
的时间复杂度。比较方便的解决了频繁删除插入带来的效率问题
把deque
理解成列表即可, 只不过列表的追加操作比较简单,但从列表头插入数据效率比较低,deque
有效解决了这个问题.
参看下面案例:
from collections import deque
q = deque(['a', 'b', 'c'])
print(q)
q.append("d")
print(q)
q.appendleft('x')
print(q)
运行结果如下:
C:\Users\Augs\Anaconda3\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2020.2.3\plugins\python-ce\helpers\pydev\pydevd.py" --multiproc --qt-support=auto --client 127.0.0.1 --port 52828 --file F:/book/python/new_python/code/te.py
pydev debugger: process 14860 is connecting
Connected to pydev debugger (build 202.7660.27)
deque(['a', 'b', 'c'])
deque(['a', 'b', 'c', 'd'])
deque(['x', 'a', 'b', 'c', 'd'])
Process finished with exit code 0
1.9.3. defaultdict¶
defaultdict
是内置数据类型dict
的一个子类,基本功能与dict
一样,只是重写了一个方法__missing__(key)
和
增加了一个可写的对象变量default_factory
。
当直接读取dict不存在的属性时,直接返回默认值
看一个例子:
d1 = {"one":1, "two":2, "three":3}
print(d1['one'])
print(d1['four'])
上面代码会出一个语法错误, 点解?
而如果用defaultdict
则不会报错,参看下面代码:
from collections import defaultdict
# lambda表达式,直接返回字符串
func = lambda: "刘大拿"
d2 = defaultdict(func)
d2["one"] = 1
d2["two"] = 2
print(d2['one'])
print(d2['four'])
运行结果如下:
C:\Users\Augs\Anaconda3\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2020.2.3\plugins\python-ce\helpers\pydev\pydevd.py" --multiproc --qt-support=auto --client 127.0.0.1 --port 53018 --file F:/book/python/new_python/code/te.py
pydev debugger: process 15108 is connecting
Connected to pydev debugger (build 202.7660.27)
1
刘大拿
Process finished with exit code 0
再打印d2["four"]的时候, 因为没有这个值,最终调用
func函数,返回字符串
刘大拿`.
1.9.4. Counter¶
Counter
统计字符串个可以支持方便、快速的计数数
例子:
from collections import Counter
# 为什么下面结果不把abcdefgabced.....作为键值,而是以其中每一个字母作为键值
# 需要括号里内容为可迭代
c = Counter("abcdefgabcdeabcdabcaba")
print(c)
s = ["liudana", "love", "love", "love", "love", "wangxiaona"]
c = Counter(s)
print(c)
运行结果如下:
C:\Users\Augs\Anaconda3\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2020.2.3\plugins\python-ce\helpers\pydev\pydevd.py" --multiproc --qt-support=auto --client 127.0.0.1 --port 53101 --file F:/book/python/new_python/code/te.py
pydev debugger: process 3560 is connecting
Connected to pydev debugger (build 202.7660.27)
Counter({'a': 6, 'b': 5, 'c': 4, 'd': 3, 'e': 2, 'f': 1, 'g': 1})
Counter({'love': 4, 'liudana': 1, 'wangxiaona': 1})
Process finished with exit code 0
1.9.5. OrderedDict¶
OrderedDict
类似于正常的词典,当在有序的词典上迭代时,返回的元素就是它们第一次添加的顺序, 及有序字典中的顺序指的是插入顺序class collections.OrderedDict
,返回已给dict
的子类,支持常规的dict
的方法OrderedDict
是一个记住元素首次插入顺序的词典,如果一个元素重写已经存在的元素,那么原始的插入位置保持不变,如果删除一个元素再重新插入,那么它就在末尾OrderedDict.popitem(last=True)
中,popitem
方法返回和删除一个(key,value)
对,如果last=True
,就以LIFO
方式执行,否则以FIFO
方式执行。OrderedDict
也支持反向迭代,例如reversed()
OrderedDict
对象之间的相等测试,例如list(od1.items()) == list(od2.items())
,是对顺序敏感的OrderedDict
和其他的映射对象(例如常规的词典)之间的相等测试是顺序不敏感的,这就允许OrderedDict
对象可以在使用常规词典的地方替换掉常规词典。OrderedDict
构造器和update()
方法可以接受关键字变量,但是它们丢失了顺序,因为Python
的函数调用机制是将一个无序的词典传入关键字变量一个有序的词典记住它的成员插入的顺序,可以使用排序函数,将其变为排序的词典
参看案例如下:
from collections import OrderedDict
a = {"banana":3,"apple":2,"pear":1,"orange":4}
print(a)
# dict sorted by key, 每个 (k,v)中按 k排序
b = OrderedDict(sorted(a.items(),key = lambda t:t[0]))
print(b)
# dict sorted by value
# 每个 (k,v)中按 v排序
c = OrderedDict(sorted(a.items(),key = lambda t:t[1]))
print(c)
# 按k的长度排序
d = OrderedDict(sorted(a.items(),key = lambda t:len(t[0])))
del d['apple']
print(d)
d["apple"] = 2
print(d)
运行结果如下:
C:\Users\Augs\Anaconda3\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2020.2.3\plugins\python-ce\helpers\pydev\pydevd.py" --multiproc --qt-support=auto --client 127.0.0.1 --port 55485 --file F:/book/python/new_python/code/te.py
pydev debugger: process 16244 is connecting
Connected to pydev debugger (build 202.7660.27)
{'banana': 3, 'apple': 2, 'pear': 1, 'orange': 4}
OrderedDict([('apple', 2), ('banana', 3), ('orange', 4), ('pear', 1)])
OrderedDict([('pear', 1), ('apple', 2), ('banana', 3), ('orange', 4)])
OrderedDict([('pear', 1), ('banana', 3), ('orange', 4)])
OrderedDict([('pear', 1), ('banana', 3), ('orange', 4), ('apple', 2)])
Process finished with exit code 0